open Gen
open Arbre_selection_instruction
open Arbreintermediaire

let nb_reg_tmp = ref (-1);;
let new_register_temp() =
  begin 
    incr nb_reg_tmp;
    RegNom("t"^(string_of_int(!nb_reg_tmp)))
  end
let pull_register_temp()=
  nb_reg_tmp := (!nb_reg_tmp-1)
;;

let table_frames = Hashtbl.create 50 ;;

let rec sel_iOperateur (op) = 
  match op with
  | IPlus -> ADD
  | IMoins -> MIN
  | IMult -> MUL
  | IDiv -> DIV
  
and sel_iComparateur (comp) = 
  match comp with
  | IInf -> BLT
  | ISup -> BGT
  | IInfEgal -> BLE
  | ISupEgal -> BGE
  | ICompEgal -> BEG
  | IDiff -> BNE

and sel_iBoolean(out,boolean) =
  begin
    print_endline "boolean";
  match boolean with
  | ITrue -> 
      LI(out,1)
  | IFalse -> 
      LI(out,0)
  (*
  | ICmp(e1,c,e2) ->
      let out_e1 = new_register_temp in
        let out_e2 = new_register_temp in
          LIST([
            sel_iExpression(out_e1,e1);
            sel_iExpression(out_e2,e2);
            OPERATION(sel_iComparateur(c),out,out_e1,out_e2)
          ])
  *)
  | INotBoolean(b) ->
      let out_b = new_register_temp() in
        let out_false = new_register_temp() in
          let instrs = 
            LIST([
              sel_iBoolean(out_b,b);
              sel_iBoolean(out_false,IFalse);
              OPERATION(NOT,out,out_b,out_false)
            ]) in
              begin
                pull_register_temp();
                pull_register_temp();
                instrs
              end
  | IOr (b1,b2) ->
      let out_b1 = new_register_temp() in
        let out_b2 = new_register_temp() in
          let instrs = LIST([
            sel_iBoolean(out_b1,b1);
            sel_iBoolean(out_b2,b2);
            OPERATION(OR,out,out_b1,out_b2)
          ])
          in
            begin
              pull_register_temp();
              pull_register_temp();
              instrs
            end
  | IAnd (b1,b2) ->
      let out_b1 = new_register_temp() in
        let out_b2 = new_register_temp() in
          let instrs = LIST([
            sel_iBoolean(out_b1,b1);
            sel_iBoolean(out_b2,b2);
            OPERATION(AND,out,out_b1,out_b2)
          ])
          in
            begin
              pull_register_temp();
              pull_register_temp();
              instrs
            end
  | _ -> NOP (* n'arrivera jamais *)
end

and sel_arguments(args,n) =
  match args with
    | [] -> []
    | arg::reste -> 
      let a = RegNom("a"^(string_of_int(n))) in
          [sel_iExpression(a,arg)]@sel_arguments(reste,n+1)

and sel_iFunCall(funcall) =
  match funcall with
  | IFunCall (nom_fonction, expressions) ->
    if (nom_fonction = "print") then 
      LIST([
        LIST(sel_arguments(expressions,0));
        PRINT
      ])
    else if (Hashtbl.mem table_frames nom_fonction) then
        let fr = Hashtbl.find table_frames nom_fonction in
          LIST([
            LIST(sel_arguments(expressions,0));
            JAL (fr.lentry)
          ])
    else
      NOP

and sel_iEgal(egal) =
  match egal with
  | IRegCons(reg,c) ->
      LI(reg,c)
  | IRegFunc(reg,funcall) ->
      LIST([
        sel_iFunCall(funcall);
        MOVE(reg,RegNom("v0"))
      ])
  | IRegExpr(reg,expr) ->
      let out_e = new_register_temp() in
        let instrs = LIST([
            sel_iExpression(out_e,expr);
            MOVE(reg,out_e)
          ]) in 
            begin
              pull_register_temp();
              instrs
            end

and sel_iBlock (IBlock(l,st)) =
      LIST([
        LABEL l;
        sel_iStatement(st)
      ])

and sel_iExpression(out,expression) =
  match(expression) with
  | IConstante(const) ->
      LI(out,const)
  | IUnop(IRegister(r)) ->
      let moins1 = RegNom("moins1") in
        LIST([
          LI(moins1,-1);
          OPERATION(MUL,out,r,moins1)
        ])
  | IUnop(expression) ->
      let out_e = new_register_temp() in
        let moins1 = RegNom("moins1") in
          let instrs = LIST([
              LI(moins1,-1);
              sel_iExpression(out_e,expression);
              OPERATION(MUL,out,out_e,moins1)
            ]) in 
            begin
              pull_register_temp();
              instrs
            end
  | IBinop (IRegister(r1),op,IRegister(r2)) ->
        OPERATION(sel_iOperateur(op),out,r1,r2)
  | IBinop (e1,op,IRegister(r2)) ->
        let out_e1 = new_register_temp() in
          OPERATION(sel_iOperateur(op),out,out_e1,r2)
  | IBinop (IRegister(r1),op,e2) ->
        let out_e2 = new_register_temp() in
          OPERATION(sel_iOperateur(op),out,r1,out_e2)
  | IBinop (e1,op,e2) ->
      let out_e1 = new_register_temp() in
        let out_e2 = new_register_temp() in
          let instrs = LIST([
              sel_iExpression(out_e1,e1);
              sel_iExpression(out_e2,e2);
              OPERATION(sel_iOperateur(op),out,out_e1,out_e2)
            ]) in
              begin
                pull_register_temp();
                pull_register_temp();
                instrs
              end
  | IRegister(reg) -> 
      MOVE (out,reg)
  | _ ->
      NOP (* n'arrivera jamais *)

and sel_jump (boolean,label_ok,label_fail) =
  begin
    print_endline "jump";
  match boolean with
    | ICmp (e1,comp,e2) ->
      let out_e1 = new_register_temp() in
        let out_e2 = new_register_temp() in
          let instrs = LIST([
              sel_iExpression(out_e1,e1);
              sel_iExpression(out_e2,e2);
              BRANCH(sel_iComparateur(comp),out_e1,out_e2,label_ok);
              J(label_fail) 
            ]) in
              begin
                pull_register_temp();
                pull_register_temp();
                instrs
              end
    | _ -> 
      let out_b = new_register_temp() in
        let zero = RegNom("zero") in
          let instrs = LIST([
              sel_iBoolean(out_b,boolean);
              BRANCH(BNE,out_b,zero,label_ok);
              J(label_fail) 
            ]) in
              begin
                pull_register_temp();
                instrs
              end
    end


and sel_iStatement_list (st_list) = 
  match st_list with
    | [] -> []
    | st::reste -> [sel_iStatement(st)]@sel_iStatement_list(reste)

and sel_iStatement (st) =
  match st with
  | IJumpIf (i) ->
      LIST([
        sel_jump(
          i.testIf,
          i.labelThen,
          i.labelElse
        );
        sel_iBlock(i.blockThen);
        sel_iBlock(i.blockElse);
        LABEL i.labelEndif;
        NOP
      ])
  | IJumpWhile (w) ->
      LIST([
        LABEL w.labelTest;
        sel_jump(
          w.testWhile,
          w.labelEnd,
          w.labelLoop
        );
        sel_iBlock(w.blockLoop)
      ])
  | IEgal (egal) ->
      sel_iEgal(egal)
  | IGoto (lb) ->
      J lb
  | INop -> 
      NOP
  | IFunc (ifuncall) ->
      sel_iFunCall(ifuncall)
  | IStatements (st_list) ->
      LIST(sel_iStatement_list(st_list))
  | IBlockStatement (block) ->
      sel_iBlock(block)

and move_arguments(args,nb) =
  match args with
    | [] -> []
    | a::r ->
      let a = RegNom("a"^(string_of_int(nb))) in
        let v = RegNom((string_of_int(nb+1))) in
          [MOVE(v,a)]@move_arguments(r,nb+1)

and modifier_block_frame(fr,blc) =
  match blc with
    | [] -> []
    | a::r ->
      match a with
        | LABEL l ->
          if (l==fr.lentry) then
                [LIST([
                  LABEL(l);
                  MOVE(RegNom("t0"),RegNom("fp"));
                  MOVE(RegNom("fp"),RegNom("sp"));
                  LI(RegNom("t1"),1);
                  OPERATION(ADD,RegNom("sp"),RegNom("sp"),RegNom("t1"));
                  MOVE(RegNom("sfp"),RegNom("t0"));
                  MOVE(RegNom("sra"),RegNom("ra"));
                  LIST(move_arguments(fr.args,0));

                ])]@modifier_block_frame(fr,r)
          else if (l==fr.lreturn) then
                [LIST([
                  LABEL(l);
                  MOVE(RegNom("v0"),RegNom("0"));
                  MOVE(RegNom("ra"),RegNom("sra"));
                  MOVE(RegNom("t0"),RegNom("sfp"));
                  MOVE(RegNom("sp"),RegNom("fp"));
                  MOVE(RegNom("fp"),RegNom("t0"));
                  JR(RegNom("ra"));
                ])]@modifier_block_frame(fr,r)
          else
              [LABEL(l)]@modifier_block_frame(fr,r)
        | LIST(l) ->
            modifier_block_frame(fr,l)@modifier_block_frame(fr,r)
        | _ -> [a]@modifier_block_frame(fr,r)

and sel_iFrame(BlockFrame(fr,block)) =
  let blc = sel_iBlock(block) in
    let blc2 = LIST(modifier_block_frame(fr,[blc])) in 
      SFrame(fr,blc2)

and sel_iFrames(frames) =
  match frames with
  | [] -> 
      []
  | f::r ->
      [sel_iFrame(f)]@sel_iFrames(r)

and enregistrer_frame(fr) =
  match fr with
    BlockFrame(f,i) -> 
        Hashtbl.add table_frames f.name f;


and hashtbl_keys h = Hashtbl.fold (fun key _ l -> key :: l) h []

and print_keys h = 
  begin
    print_endline "";
    print_string "keys=[";
    List.iter (fun key -> print_string (key^",") ) (hashtbl_keys h);
    print_endline "]";
    print_endline ""
  end

and enregistrer_frames(frames) =
  match frames with
  | [] -> print_string("")
  | f::r ->
    begin
      enregistrer_frame(f);
      enregistrer_frames(r);
      print_string("")
    end

and sel_iProgramme(prog) =
  begin
    enregistrer_frames(prog.frames);
    let st = sel_iBlock(prog.text) in
      let sf = sel_iFrames(prog.frames) in
          {
            stext=st;
            sframes=sf;
          }
  end
;;


(* 
  ajouter :
    - prologue et épilogue,
*)